// *** LICENSE HEADER ***
// Filename: ./particleemitter.cpp
// This file is part of Nodewarz 0.1
// Nodewarz is a 3d OpenGL strategy game
// Copyright (C) 2007 CZestmyr
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// *** END LICENSE HEADER ***
#include "particleemitter.h"

particleEmitter::particleEmitter(float newX, float newY, float newZ, GLuint newTex,
                                 int t,
                                 float otherX,
                                 float otherY,
                                 float otherZ,
                                 int newLifetime, int newEmitEnd,
                                 int newParticleLifetimeCenter,
                                 int newParticleLifetimeVariance,
                                 int newParticleFadeTime,
                                 int newParticlesPerSecond,
                                 float newParticleSize,
                                 float newParticleSpeedX,
                                 float newParticleSpeedXVariance,
                                 float newParticleSpeedY,
                                 float newParticleSpeedYVariance,
                                 float newParticleSpeedZ,
                                 float newParticleSpeedZVariance,
                                 float newParticleColorRed,
                                 float newParticleColorGreen,
                                 float newParticleColorBlue,
                                 float newParticleFadeColorRed,
                                 float newParticleFadeColorGreen,
                                 float newParticleFadeColorBlue) {
    texture = newTex;
    x = newX;
    y = newY;
    z = newZ;

    lifetime = newLifetime;
    emitEnd = newEmitEnd;
    particleLifetimeCenter = newParticleLifetimeCenter;
    particleLifetimeVariance = newParticleLifetimeVariance;
    particleFadeTime = newParticleFadeTime;


    //Set the particle emmiter data depending on the chosen template
    switch (t) {
        case _PET_FIRE:
            particlesPerSecond = 150;
            theAff = new flameAffector(newX, newY, 0.001f);
            particleSize = 0.1f;
            particleSpeedX = 0.0f;
            particleSpeedXVariance = 0.7f;
            particleSpeedY = 0.0f;
            particleSpeedYVariance = 0.7f;
            particleSpeedZ = 1.0f;
            particleSpeedZVariance = 0.5f;
            particleColorRed = 1.0f;
            particleColorGreen = 0.8f;
            particleColorBlue = 0.0f;
            particleFadeColorRed = 0.8f;
            particleFadeColorGreen = 0.2f;
            particleFadeColorBlue = 0.2f;
            break;
        case _PET_SMOKE:
            particlesPerSecond = 100;
            theAff = new gravityAffector(0.0018f);
            particleSize = 0.3f;
            particleSpeedX = 0.0f;
            particleSpeedXVariance = 0.8f;
            particleSpeedY = 0.0f;
            particleSpeedYVariance = 0.8f;
            particleSpeedZ = 0.8f;
            particleSpeedZVariance = 0.3f;
            particleColorRed = 1.0f;
            particleColorGreen = 1.0f;
            particleColorBlue = 1.0f;
            particleFadeColorRed = 0.0f;
            particleFadeColorGreen = 0.0f;
            particleFadeColorBlue = 0.0f;
            emitEnd = 1000;
            lifetime = 3000;
            particleLifetimeCenter = 1000;
            particleLifetimeVariance = 800;
            particleFadeTime = 1500;
            break;
        case _PET_FOUNTAIN:
            particlesPerSecond = 50;
            theAff = new gravityAffector(0.0020f);
            particleSize = 0.05f;
            particleSpeedX = 0.0f;
            particleSpeedXVariance = 0.15f;
            particleSpeedY = 0.0f;
            particleSpeedYVariance = 0.15f;
            particleSpeedZ = 1.1f;
            particleSpeedZVariance = 0.2f;
            particleColorRed = 0.3f;
            particleColorGreen = 0.6f;
            particleColorBlue = 1.0f;
            particleFadeColorRed = 0.0f;
            particleFadeColorGreen = 1.0f;
            particleFadeColorBlue = 0.0f;
            emitEnd = 0;
            lifetime = 0;
            particleLifetimeCenter = 1000;
            particleLifetimeVariance = 1;
            particleFadeTime = 500;
            break;
        case _PET_DOLLARS:
            particlesPerSecond = 1;
            theAff = 0;
            particleSize = 0.1f;
            particleSpeedX = 0.0f;
            particleSpeedXVariance = 0.3f;
            particleSpeedY = 0.0f;
            particleSpeedYVariance = 0.3f;
            particleSpeedZ = 0.7f;
            particleSpeedZVariance = 0.2f;
            particleColorRed = 1.0f;
            particleColorGreen = 1.0f;
            particleColorBlue = 0.0f;
            particleFadeColorRed = 0.0f;
            particleFadeColorGreen = 0.5f;
            particleFadeColorBlue = 0.0f;
            emitEnd = 0;
            lifetime = 0;
            particleLifetimeCenter = 2000;
            particleLifetimeVariance = 1;
            particleFadeTime = 1500;
            break;
        case _PET_POWERSPHERE:
            particlesPerSecond = 500;
            theAff = new sphereRotationAffector(newX, newY, 0.3f, 0.01f, 0.6f);
            particleSize = 0.02f;
            particleSpeedX = 0.0f;
            particleSpeedXVariance = 0.0f;
            particleSpeedY = 0.0f;
            particleSpeedYVariance = 0.0f;
            particleSpeedZ = 0.51f;
            particleSpeedZVariance = 0.5f;
            particleColorRed = 0.0f;
            particleColorGreen = 0.9f;
            particleColorBlue = 0.3f;
            particleFadeColorRed = 0.0f;
            particleFadeColorGreen = 0.3f;
            particleFadeColorBlue = 0.7f;
            particleLifetimeCenter = 1000;
            particleLifetimeVariance = 300;
            particleFadeTime = 200;
            emitEnd = 1500;
            lifetime = 3000;
            break;
        case _PET_PSIBURST:
            particlesPerSecond = 50;
            theAff = 0;
            particleSize = 0.1f;
            particleSpeedX = otherX - x;
            particleSpeedY = otherY - y;
            particleSpeedZ = otherZ - z;
            particleSpeedXVariance = 0.05f;
            particleSpeedYVariance = 0.05f;
            particleSpeedZVariance = 0.05f;
            particleColorRed = 0.2f;
            particleColorGreen = 0.0f;
            particleColorBlue = 0.5f;
            particleLifetimeCenter = 1000;
            particleLifetimeVariance = 1;
            particleFadeTime = 0;
            emitEnd = 800;
            lifetime = 2000;
            break;
        case _PET_FIRE_IMMORTAL:
            particlesPerSecond = 70;
            theAff = new flameAffector(newX, newY, 0.001f);
            particleSize = 0.04f;
            particleSpeedX = 0.0f;
            particleSpeedXVariance = 0.4f;
            particleSpeedY = 0.0f;
            particleSpeedYVariance = 0.4f;
            particleSpeedZ = 0.35f;
            particleSpeedZVariance = 0.2f;
            particleColorRed = 1.0f;
            particleColorGreen = 0.8f;
            particleColorBlue = 0.0f;
            particleFadeColorRed = 0.6f;
            particleFadeColorGreen = 0.0f;
            particleFadeColorBlue = 0.0f;
            particleLifetimeCenter = 1200;
            particleLifetimeVariance = 800;
            particleFadeTime = 1000;
            lifetime = 0;
            emitEnd = 0;
            break;
        case _PET_SPHERE_IMMORTAL:
            particlesPerSecond = 150;
            theAff = new sphereRotationAffector(newX, newY, newZ + 0.1f, 0.001f, 0.1f);
            particleSize = 0.02f;
            particleSpeedX = 0.0f;
            particleSpeedXVariance = 0.0f;
            particleSpeedY = 0.0f;
            particleSpeedYVariance = 0.0f;
            particleSpeedZ = 0.15f;
            particleSpeedZVariance = 0.0f;
            particleColorRed = 0.0f;
            particleColorGreen = 0.9f;
            particleColorBlue = 0.3f;
            particleFadeColorRed = 0.0f;
            particleFadeColorGreen = 0.3f;
            particleFadeColorBlue = 0.7f;
            particleLifetimeCenter = 1320;
            particleLifetimeVariance = 32;
            particleFadeTime = 0;
            emitEnd = 0;
            lifetime = 0;
            break;
        case _PET_PSIBURST_IMMORTAL:
            particlesPerSecond = 60;
            theAff = 0;
            particleSize = 0.01f;
            particleSpeedX = otherX - x;
            particleSpeedY = otherY - y;
            particleSpeedZ = otherZ - z;
            particleSpeedXVariance = 0.02f;
            particleSpeedYVariance = 0.02f;
            particleSpeedZVariance = 0.02f;
            particleColorRed = 0.2f;
            particleColorGreen = 0.0f;
            particleColorBlue = 0.5f;
            particleLifetimeCenter = 1000;
            particleLifetimeVariance = 1;
            particleFadeTime = 0;
            emitEnd = 0;
            lifetime = 0;
            break;
        case _PET_OTHER:
        default:
            particlesPerSecond = newParticlesPerSecond;
            theAff = 0;
            particleSpeedX = newParticleSpeedX;
            particleSpeedXVariance = newParticleSpeedXVariance;
            particleSpeedY = newParticleSpeedY;
            particleSpeedYVariance = newParticleSpeedYVariance;
            particleSpeedZ = newParticleSpeedZ;
            particleSpeedZVariance = newParticleSpeedZVariance;
            particleColorRed = newParticleColorRed;
            particleColorGreen = newParticleColorGreen;
            particleColorBlue = newParticleColorBlue;
            particleFadeColorRed = newParticleFadeColorRed;
            particleFadeColorGreen = newParticleFadeColorGreen;
            particleFadeColorBlue = newParticleFadeColorBlue;
            particleSize = newParticleSize / 2.0f;
            break;
    }

    particles = 0;
    freeTime = 0;
    age = 0;
    srand(time(0));
}

particleEmitter::~particleEmitter() {
    particle* it = particles;
    particle* del;
    while (it != 0) {
        del = it;
        it = it->next;
        delete del;
    }
    if (theAff)
        delete theAff;
}

void particleEmitter::SetParentData(particleSystem* newParent, int newHandle) {
    parent = newParent;
    myHandle = newHandle;
}

void particleEmitter::Update(int dt) {
    particle* it = particles;
    age += dt;

    //Commit a suicide ;-) (if lifetime == 0, live until parent kills you)
    if (age > lifetime && lifetime != 0) {
        parent->RemoveEmitter(myHandle);
        return;
    }

    //Spawn new particles (if emitEnd == 0, spawn particles forever)
    if (age < emitEnd || emitEnd == 0) {
        freeTime += dt;
        int newParticleNum = freeTime * particlesPerSecond / 1000;
        freeTime -= newParticleNum * 1000 / particlesPerSecond;
        for (int i = 0; i < newParticleNum; ++i) {
            it = new particle(x, y, z,
                              RandomFloat(particleSpeedX - particleSpeedXVariance,
                                          particleSpeedX + particleSpeedXVariance),
                              RandomFloat(particleSpeedY - particleSpeedYVariance,
                                          particleSpeedY + particleSpeedYVariance),
                              RandomFloat(particleSpeedZ - particleSpeedZVariance,
                                          particleSpeedZ + particleSpeedZVariance),
                              rand() % (particleLifetimeVariance*2) +
                              particleLifetimeCenter - particleLifetimeVariance,
                              particleFadeTime,
                              particleSize,
                              theAff, texture,
                              particleColorRed, particleColorGreen, particleColorBlue,
                              particleFadeColorRed,
                              particleFadeColorGreen,
                              particleFadeColorBlue, 1.0f);
            it->SetNext(particles);
            particles = it;
        }
    }

    //Update all particles (even the new ones) and delete those that have
    //exceeded their life length
    particle* del;
    particle* prev;
    it = particles;
    prev = 0;
    bool remain;
    while (it != 0) {
        remain = it->Update(dt);
        //The particle needs to be deleted
        if (!remain) {
            del = it;
            it = it->next;
            //We are deleting the first particle in the list
            if (prev == 0) {
                particles = it;
                //Or just any other particle
            } else {
                prev->SetNext(it);
            }
            delete del;
            //The particle will survive for now
        } else {
            prev = it;
            it = it->next;
        }
    }
}

void particleEmitter::Draw() {
    particle* it = particles;

    //Enable aditive alpha blending of particle
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);

    //Bind the correct particle texture
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, texture);

    glEnable(GL_DEPTH_TEST);
    //Enable depth buffer mask to leave depth buffer untouched by particles
    glDepthMask(GL_FALSE);

    //Save the modelview matrix for particle billboard calculations
    particle::SetModelviewMatrix();

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();

    //Draw all the particles
    while (it != 0) {
        it->Draw();
        it = it->next;
    }

    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    glDepthMask(GL_TRUE);
}

float particleEmitter::RandomFloat(float minVal, float maxVal) {
    return (rand() % 1000) *(maxVal - minVal) / 1000.0f + minVal;
}
